package jmine.tec.aom.services.include;

import jmine.tec.aom.AOMMessages;
import jmine.tec.aom.dao.PropertyDAO;
import jmine.tec.aom.domain.Property;
import jmine.tec.aom.domain.PropertyGroup;
import jmine.tec.aom.domain.PropertyType;
import jmine.tec.database.pack.PackException;
import jmine.tec.database.pack.services.PackEntityPersistenceHelper;
import jmine.tec.di.annotation.Injected;
import jmine.tec.services.annotations.Execution;
import jmine.tec.services.annotations.Input;
import jmine.tec.services.api.RawInput;
import jmine.tec.services.api.ServiceValidationExecutionException;
import jmine.tec.services.api.export.ReferenceMap;
import jmine.tec.services.api.export.ServiceFiller;
import jmine.tec.services.export.impl.ExportUtils;
import jmine.tec.services.io.ServiceBean;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Abstract service for creating {@link Property}.
 * 
 * @param <P> the {@link Property} type
 * @author jrenaut
 */
public abstract class AbstractPropertyService<P extends Property> implements ServiceFiller<P> {

    public static final String NAME = "Name";

    public static final String MNEMONIC = "Mnemonic";

    public static final String DESCRIPTION = "Description";

    public static final String GROUP = "Group";

    public static final String DATABASE_ID = "Database ID";

    private PackEntityPersistenceHelper entityHelper;

    private PropertyDAO propertyDAO;

    /**
     * Creates a new {@link Property}.
     * 
     * @param props the {@link RawInput}
     * @param description the description
     * @param group a {@link PropertyGroup}
     * @param databaseID the database ID for this {@link Property}
     * @throws PackException if an error occurs while saving the {@link Property} with the given ID
     */
    @Execution
    public final void execute(@Injected RawInput props, @Input(fieldName = DESCRIPTION) String description,
            @Input(fieldName = GROUP) PropertyGroup group, @Input(fieldName = DATABASE_ID, required = false) Long databaseID)
            throws PackException {
        String mnemonic = props.getRaw(MNEMONIC);
        if (mnemonic == null || mnemonic.trim().length() == 0) {
            throw new ServiceValidationExecutionException(AOMMessages.FIELD_REQUIRED.create(MNEMONIC));
        }

        String name = props.getRaw(NAME);
        if (name == null || name.trim().length() == 0) {
            throw new ServiceValidationExecutionException(AOMMessages.FIELD_REQUIRED.create(NAME));
        }

        P property;
        try {
            property = this.getPropertyClass().cast(this.propertyDAO.findByGroupAndMnemonic(group, mnemonic));
        } catch (BeanNotFoundException e) {
            property = this.entityHelper.findOrCreate(this.getPropertyClass(), databaseID, null);
        }

        property.setName(name.trim());
        property.setMnemonic(mnemonic.trim());
        property.setDescription(description);
        property.setPropertyType(this.getPropertyType());
        property.setPropertyGroup(group);

        this.executeSpecific(property);

        this.entityHelper.save(property, databaseID);
    }

    /**
     * May be overwritten to add specific behavior.
     * 
     * @param property the {@link Property}
     */
    protected void executeSpecific(P property) {
    }

    /**
     * {@inheritDoc}
     */
    public void fillServiceBean(ServiceBean bean, ReferenceMap referenceMap, P entity) {
        ExportUtils.setService(bean, this.getClass());
        ExportUtils.put(bean, NAME, entity.getName());
        ExportUtils.put(bean, MNEMONIC, entity.getMnemonic());
        ExportUtils.put(bean, DESCRIPTION, entity.getDescription());
        ExportUtils.put(bean, GROUP, referenceMap, entity.getPropertyGroup(), entity.getPropertyGroup().getMnemonic());
        ExportUtils.putDatabaseId(bean, DATABASE_ID, entity);
        this.fillSpecificServiceBean(bean, referenceMap, entity);
    }

    /**
     * Fills specifics values of the concrete service.
     * 
     * @param bean the {@link ServiceBean}
     * @param referenceMap the {@link ReferenceMap}
     * @param entity the {@link Property}
     */
    protected abstract void fillSpecificServiceBean(ServiceBean bean, ReferenceMap referenceMap, P entity);

    /**
     * @return the {@link PropertyType}
     */
    protected abstract PropertyType getPropertyType();

    /**
     * @return the actual {@link Property} class
     */
    protected abstract Class<P> getPropertyClass();

    /**
     * @param entityHelper the entityHelper to set
     */
    @Injected
    public void setEntityHelper(PackEntityPersistenceHelper entityHelper) {
        this.entityHelper = entityHelper;
    }

    /**
     * @param propertyDAO the propertyDAO to set
     */
    @Injected
    public void setPropertyDAO(PropertyDAO propertyDAO) {
        this.propertyDAO = propertyDAO;
    }
}
